﻿namespace CloudNinja.Metering.Providers.SQLAzure
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;

    using CloudNinja.Metering.Common;
    using CloudNinja.Metering.Common.Configuration;
    using CloudNinja.Metering.Common.Logging;
    using CloudNinja.Metering.Data;

    using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling;
    using Microsoft.Practices.EnterpriseLibrary.WindowsAzure.TransientFaultHandling.SqlAzure;

    using MeterData = CloudNinja.Metering.Data.Models.MeterData;

    [ProviderConfigElementType("CloudNinja.Metering.Providers.Configuration.DatabaseSize.DatabaseSizeMeteringProviderElement, CloudNinja.Metering.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")]
    public class DatabaseSizeProvider : MeteringProviderBase
    {
        private readonly Dictionary<string, string> databaseServers;

        private readonly ILogger logger;

        public DatabaseSizeProvider(TimeSpan collectInterval, Dictionary<string, string> databaseServers, ILogger logger, IMeteringRepository repository, IMeteringContext meteringContext)
            : base(collectInterval, repository, logger, meteringContext)
        {
            this.databaseServers = databaseServers;
            this.logger = logger;
        }

        public override string Name
        {
            get { return "DatabaseSizeProvider"; }
        }

        public override void Initialize()
        {
            this.logger.LogInformation("Initialized DatabaseSize Provider");
        }

        public override void Collect()
        {
            var results = new Dictionary<string, long>();
            var lockObject = new object();

            this.StartTiming();
            using (var sem = new SemaphoreSlim(1))
            {
                Parallel.ForEach(
                    this.databaseServers.Keys,
                    () => new Dictionary<string, long>(),
                    (connStr, loopstate, localDict) =>
                    {
                        var databaseNames = this.GetDatabases(string.Format("{0};Initial Catalog=master", connStr));

                        foreach (var databaseName in databaseNames)
                        {
                            var tenantId = Helper.GetTenantId(databaseName, this.databaseServers[connStr]);
                            if (string.IsNullOrEmpty(tenantId))
                            {
                                continue;
                            }

                            var dbSize =
                                TenantDatabaseSize.GetDatabaseSize(
                                    string.Format("{0};Initial Catalog={1}", connStr, databaseName), this.logger, this.MeteringContext);
                            localDict.Add(tenantId, dbSize);
                        }

                        return localDict;
                    },
                    finalResult =>
                    {
                        lock (lockObject)
                        {
                            foreach (var l in finalResult)
                            {
                                results.Add(l.Key, l.Value);
                            }
                        }
                    });
            }

            this.MeteringRepository.Write(results.Select(x => new MeterData
            {
                MeterTypeId = "dbs",
                TenantId = x.Key,
                Value = x.Value,
                TimePeriod = DateTimeOffset.Now.UtcDateTime,
                Count = 0
            }));

            this.StopTiming();
        }

        public override void Stop()
        {
            // We have nothing we can or need to end/stop here.
        }

        private IEnumerable<string> GetDatabases(string connStr)
        {
            var databaseNames = new List<string>();

            using (var connection = new ReliableSqlConnection(connStr, this.MeteringContext.SqlAzureConnectionRetryPolicy, this.MeteringContext.SqlAzureCommandRetryPolicy))
            {
                using (var command = connection.CreateCommand())
                {
                    try
                    {
                        connection.Open();

                        command.CommandText = @"select name from sys.databases";
                        command.CommandType = System.Data.CommandType.Text;

                        using (var reader = command.ExecuteReaderWithRetry(this.MeteringContext.SqlAzureCommandRetryPolicy))
                        {
                            while (reader.Read())
                            {
                                databaseNames.Add((string)reader["name"]);
                            }
                        }
                    }
                    catch (Exception exception)
                    {
                        this.logger.LogException(exception);
                    }
                }
            }

            return databaseNames;
        }
    }
}